﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using Jayden.Dll.Cards.Poker.Enums;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

namespace Jayden.Dll.Cards.Poker
{
	public class PokerSocket
	{
		private Socket m_Socket;
		private NetworkStream m_NetworkStream;
		private SslStream m_SslStream;

		public bool Encrypted = false;

		public PokerSocket(Socket socket)
		{
			m_Socket = socket;
			m_NetworkStream = new NetworkStream(m_Socket);
			m_SslStream = new SslStream(m_NetworkStream, true, delegate(object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; });
		}

		public Socket Socket { get { return m_Socket; } }
		public NetworkStream NetworkStream { get { return m_NetworkStream; } }
		public SslStream SslStream { get { return m_SslStream; } }

		public EndPoint RemoteEndPoint { get { return m_Socket.RemoteEndPoint; } }
		public int Available { get { return m_Socket.Available; } }
		public bool Connected { get { return m_Socket.Connected; } }

		private ulong ReadUnsignedNumber()
		{
			ulong value = 0;
			byte oneByte = ReadByte();
			int shift = 0;
			while ((oneByte & 0x80) != 0x00)
			{
				value |= (ulong)(oneByte & 0x7f) << (shift * 7);
				shift++;
				oneByte = ReadByte();
			}
			value |= (ulong)(oneByte & 0x7f) << (shift * 7);
			return value;
		}
		private void WriteUnsignedNumber(ulong value)
		{
			ulong filter = 0x7f;
			byte byteValue = (byte)(value & filter);
			value = value >> 7;
			while (value > 0)
			{
				byteValue |= 0x80;
				WriteByte(byteValue);
				byteValue = (byte)(value & filter);
				value = value >> 7;
			}
			WriteByte(byteValue);
		}

		public byte[] ReadBytes(int count)
		{
			byte[] buffer = new byte[count];
			int read = 0;
			if (!Encrypted)
			{
				do
				{
					read += m_Socket.Receive(buffer, read, buffer.Length - read, SocketFlags.None);
				} while (read < buffer.Length);
			}
			else
			{
				do
				{
					read += m_SslStream.Read(buffer, read, buffer.Length - read);
				} while (read < buffer.Length);
			}
			return buffer;
		}

		public byte ReadByte() { return ReadBytes(1)[0]; }
		public ulong ReadUInt64() { return ReadUnsignedNumber(); }
		public int ReadInt32() { return (int)ReadUnsignedNumber(); }
		public Card ReadCard()
		{
			int value = (int)ReadByte();
			if (value == 0xff)
				return null;
			return Card.Parse(1ul << value);
		}
		public string ReadString()
		{
			ulong size = ReadUnsignedNumber();
			if (size == 0)
				return string.Empty;
			if (size > 1024)
				throw new Exception("size > 1024");
			byte[] buffer = ReadBytes((int)size);
			return Encoding.UTF8.GetString(buffer);
		}
		public bool ReadBoolean() { return ReadByte() != 0x00; }
		public CardList ReadCards()
		{
			byte count = ReadByte();
			CardList list = new CardList();
			for (byte c = 0; c < count; c++)
				list.Add(ReadCard());
			return list;
		}
		public Image ReadImage()
		{
			int length = ReadInt32();
			byte[] data = ReadBytes(length);
			using (MemoryStream stream = new MemoryStream(data))
			{
				return Image.FromStream(stream);
			}
		}

		public DateTime ReadDateTime()
		{
			long ticks = (long)ReadUInt64();
			return new DateTime(ticks);
		}

		public void WriteBytes(byte[] bytes)
		{
			if (!Encrypted)
			{
				m_Socket.Send(bytes, 0, bytes.Length, SocketFlags.None);
			}
			else
			{
				m_SslStream.Write(bytes, 0, bytes.Length);
			}
		}

		public void WriteByte(byte value) { WriteBytes(new byte[] { value }); }
		public void WriteUInt64(ulong value) { WriteUnsignedNumber(value); }
		public void WriteInt32(int value) { WriteUnsignedNumber((ulong)value); }
		public void WriteCard(Card card)
		{
			if(card == null)
				WriteByte((byte)0xff);
			else
				WriteByte((byte)card.BitPosition);
		}
		public void WriteString(string value)
		{
			if (!string.IsNullOrWhiteSpace(value))
			{
				byte[] bytes = Encoding.UTF8.GetBytes(value);
				if (bytes.Length > 1024)
					throw new Exception("bytes.Length > 1024");
				WriteUnsignedNumber((ulong)bytes.Length);
				WriteBytes(bytes);
			}
			else
			{
				WriteByte((byte)0);
			}
		}
		public void WriteBoolean(bool value) { WriteByte(value ? (byte)0xff : (byte)0x00); }
		public void WriteCards(CardList cards)
		{
			WriteByte((byte)cards.Count);
			for (int c = 0; c < cards.Count; c++)
				WriteCard(cards[c]);
		}
		public void WriteImage(Image image)
		{
			using (MemoryStream stream = new MemoryStream())
			{
				image.Save(stream, ImageFormat.Png);
				WriteInt32((int)stream.Position);
				WriteBytes(stream.ToArray());
			}
		}
		public void WriteDateTime(DateTime dateTime)
		{
			WriteUInt64((ulong)dateTime.Ticks);
		}

		
		public void SendMessage(string message)
		{
			lock (this)
			{
				WriteInt32((int)PokerProtocolMessageType.Message);
				WriteString(message);
			}
		}


		public void KeepAlive()
		{
			lock (this)
			{
				WriteInt32((int)PokerProtocolMessageType.KeepAlive);
			}
		}
	}
}

